package boxync;

import java.sql.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

public class Server {
    public static final String IP_ADDRESS = "127.0.0.1";
    public static final int PORT = 5000;
    
    /**
     * BoxyncDir utama tempat Server menyimpan file milik berbagai Client
     */
    public static final String SERVER_DIRECTORY = "C:/boxync";
    
    public static final String DELETED_LOG_FILENAME = "__deleted.txt";
    
    public static final String DB_HOST = "localhost";
    public static final int DB_PORT = 3306;
    public static final String DB_NAME = "boxync";
    public static final String DB_USER = "progin";
    public static final String DB_PASSWORD = "progin";
    
    /**
     * Koneksi ke basis data
     */
    public Connection con;
    
    /**
     * 0 jika server tidak jalan, 1 jika server jalan
     */
    public byte started;
    
    /**
     * ServerSocket untuk menerima request login dari Client
     */
    public ServerSocket serverSocket;
    
    /**
     * Thread-thread yg berhubungan dengan Client yg telah login
     */
    public ArrayList<ServerThread> connectedClients;
    
    public Server() {
        con = createConnection();
        
        started = 0;
        connectedClients = new ArrayList<ServerThread>();
        try {
            serverSocket = new ServerSocket(PORT);
        } catch (IOException e) {
            System.err.println("IOExc in Server.Server()");
        }
    }
    
    /**
     * Membuat koneksi ke database
     * @return objek Connection ke database
     */
    public final Connection createConnection() {
        Connection c = null;
        String conString = "jdbc:mysql://" + DB_HOST + ":" + DB_PORT + "/"
                + DB_NAME + "?user=" + DB_USER;
        
        if (!DB_PASSWORD.equals("")) {
            conString += "&password=" + DB_PASSWORD;
        }
        try {
            c = DriverManager.getConnection(conString);
        } catch (SQLException e) {
            System.out.println("SQLExc in Server.createConnection()");
        }
        
        return c;
    }
    
    /**
     * Start server
     */
    public void start() {
        started = 1;
        while (true) {
            try {
                Socket socket = serverSocket.accept();
                handleLogin(socket);
            } catch (IOException e) {
                System.out.println("IOExc in Server.start()");
            }
        }
    }
    
    /**
     * Matikan server dan close semua Socket di connectedClients
     */
    public void stop() {
        for (ServerThread st : connectedClients) {
            try {
                st.stopped = true;
                st.in.close();
                st.out.close();
                st.socket.close();
            } catch (IOException e) {
                System.err.println("IOExc in Server.stop()");
            }
        }
        
        connectedClients.clear();
    }
    
    /**
     * 
     * @param socket Socket yg ingin dihandle loginnya
     * @return true jika login berhasil 
     */
    public boolean handleLogin(Socket socket) {
        String username = null, password = null, s = null, dir = null;
        ObjectInputStream in = null;
        ObjectOutputStream out = null;
        
        try {
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
            
            s = (String)in.readObject();
            if (!s.equals(BoxyncProtocol.LOGIN_REQUEST))
                return false;
            
            BoxyncSecurity sec = new BoxyncSecurity();
            
            username = readFromSocket(socket, in);
            username = sec.decrypt(username);
            password = readFromSocket(socket, in);
            password = sec.decrypt(password);
            
            dir = login(username, password);
            if (!dir.equals("")) {
                ServerThread st = new ServerThread(new BoxyncDir(
                        null, SERVER_DIRECTORY + "/" + username));
                st.socket = socket;
                st.in = in;
                st.out = out;
                FileHelper.readDeletedLog(st.dir);
                connectedClients.add(st);
                st.start();
                
                writeToSocket(socket, out, BoxyncProtocol.LOGIN_ACCEPT);
                writeToSocket(socket, out, dir);
                return true;
            } else {
                writeToSocket(socket, out, BoxyncProtocol.LOGIN_DENY);
                return false;
            }
        } catch (IOException e) {
            System.err.println("IOExc in Server.handleLogin()");
            return false;
        } catch (ClassNotFoundException e) {
            System.out.println(e);
            return false;
        }
    }
    
    /**
     * Koneksi ke database untuk mengambil direktori kerja user
     * @param username
     * @param password
     * @return Path direktori kerja user atau string kosong jika login gagal
     */
    public String login(String username, String password) {
       String dir = "";
       Statement st = null;
       
       try {
           st = con.createStatement();
           String query = "SELECT * FROM User WHERE username='" + username
                   + "' AND password='" + password + "'";
           ResultSet rs = st.executeQuery(query);
           if (!rs.first())
               return dir;
           
           dir = rs.getString("path");
       } catch (SQLException e) {
           System.err.println("SQLExc in Server.login()");
       } finally {
           try {
               if (st != null) st.close();
           } catch (SQLException e) {
               System.err.println("SQLExc in Server.login()");
           }
       }
       
       return dir;
    }
    
    /**
     * Mengembalikan hasil pembacaan in sebanyak satu baris dan mencetak ke System.out
     * @param socket Socket yg ingin dibaca
     * @param in BufferedReader yg ingin dibaca
     * @return satu baris String hasil pembacaan
     */
    public String readFromSocket(Socket socket, ObjectInputStream in) {
        String result = null;
        try {
            result = (String)in.readObject();
        } catch (IOException e) {
            System.err.println("IOExc in Server.readFromSocket()");
        } catch (ClassNotFoundException e) {
            System.err.println(e);
        }
        System.out.println("Read from " + socket.getPort() + ": " + result);
        
        return result;
    }
    
    /**
     * Menulis String str ke socket dan mencetak str ke System.out
     * @param socket Socket yg ingin ditulis
     * @param out PrintWriter dari socket
     * @param str String yg ingin ditulis
     */
    public void writeToSocket(Socket socket, ObjectOutputStream out, String str) {
        System.out.println("Write to " + socket.getPort() + ": " + str);
        try {
            out.writeObject(str);
            out.flush();
        } catch(IOException e) {
            System.out.println(e);
        }
    }
    
    public static void main(String[] args) {
        Server s = new Server();
        s.start();
    }
    
    /**
     * Thread untuk sinkronisasi server/client
     */
    class ServerThread extends BoxyncThread {
        public ServerThread(BoxyncDir _dir) {
            super(_dir);
        }
        
        public ServerThread(Socket _socket, BoxyncDir _dir) {
            super(_socket, _dir);
            // BEL#UM deleted = readDeletedLog()
        }
        
        public ArrayList<BoxyncFile> deleted;
        
        @Override
        public void run() {
            while (!stopped) {
                try {
                    exchangeUpdates();
                    System.out.println("");
                } catch (BoxyncException e) {
                    System.out.println(e);
                }
            }
        }
        
        /**
         * Bertukar update dengan Client, dimulai dengan Server mendorong update,
         * kemudian Server meminta update
         */
        @Override
        public void exchangeUpdates() throws BoxyncException {
            syncWithExplorer();
            ArrayList<String> updates = pullUpdates();
            for (String update : updates) {
                handleUpdate(update);
            }
            
            syncWithExplorer();
            pushUpdates();
        }
                
        /**
         * Mengembalikan hasil pembacaan in sebanyak satu baris dari Client dan mencetak ke System.out
         * @return satu baris String hasil pembacaan
         */
        @Override
        public String read() {
            String result = null;
            boolean loop;
            do {
                loop = false;
                try {
                    result = (String)in.readObject();
                    System.out.println("Read from " + socket.getPort() + ": " + result);
                    if (result.equals(BoxyncProtocol.LOGOUT)) {
                        handleLogout();
                    } else if (result.equals(BoxyncProtocol.REQUEST_UPDATE)) {
                        handleDownload();
                        loop = true;
                    }
                } catch (IOException e) {
                    System.err.println("IOExc in Server.read()");
                } catch (BoxyncException e) {
                    System.err.println(e);
                } catch (ClassNotFoundException e) {
                    System.err.println(e);
                } catch (Exception e) {
                    System.err.println(e);
                }
            } while (loop == true);

            return result;
        }
        
        /**
         * Mengembalikan hasil pembacaan in sebanyak satu baris dari Client tanpa mencetak ke System.out
         * @return satu baris String hasil pembacaan
         */
        @Override
        public String readWOPrint() {
            String result = null;
            boolean loop;
            do {
                loop = false;
                try {
                    result = (String)in.readObject();
                    if (result.equals(BoxyncProtocol.LOGOUT)) {
                        handleLogout();
                    } else if (result.equals(BoxyncProtocol.REQUEST_UPDATE)) {
                        handleDownload();
                        loop = true;
                    }
                } catch (IOException e) {
                    System.err.println("IOExc in Server.read()");
                } catch (BoxyncException e) {
                    System.err.println(e);
                } catch (ClassNotFoundException e) {
                    System.err.println(e);
                } catch (Exception e) {
                    System.err.println(e);
                }
            } while (loop == true);

            return result;
        }
        
        /**
         * Handler untuk Client yg logout
         */
        public void handleLogout() {
            stopped = true;
            try {
                in.close();
                out.close();
                socket.close();
            } catch (IOException e) {
                System.err.println("IOExc in ClientThread.handleLogout()");
            }
        }
        
        @Override
        public void handleUpdate(String update) {
            FileInfo fi = dir.searchFile(FileInfo.getFileName(update));
            try {
                if (fi != null) {
                    if (BoxyncFile.isDeleted(update)) {
                        System.out.println("DELETING FILE AND UPDATING DELETED LOG");
                        fi.delete();
                        fi.timeStamp = new Date();
                        fi.timeStamp.setYear(fi.timeStamp.getYear() + 1900);
                        FileHelper.writeDeletedLog(dir);
                    } else {
                        System.out.println("REQUESTING FILE DOWNLOAD");
                        requestDownload(update);
                    }
                } else {
                    if (BoxyncFile.isDeleted(update)) {
                        System.out.println("UPDATING DELETED LOG");
                        FileHelper.writeDeletedLog(dir);
                    } else {
                        System.out.println("REQUESTING FILE DOWNLOAD");
                        requestDownload(update);
                    }
                }
            } catch (BoxyncException e) {
                System.err.println(e);
            }
        }
        
        /**
         * 
         * @param o ClientThread yg ingin dicek kesamaannya
         * @return true jika port kedua ClientThread sama
         */
        @Override
        public boolean equals(Object o) {
            ServerThread ct = (ServerThread)o;
            
            if (ct == null)
                return false;
            
            return socket.getLocalPort() == ct.socket.getLocalPort();
        }
    }
}
